#include "Method.h"
#include <cmath>
#include <algorithm>
#include <limits>
#include "Info.h"

/************************************************************************************************
*
*	Split
*	Split string by delimiter
*	Return: vector int
*	input:	string
*	delimiter
*
*************************************************************************************************/
vector<int> Method::Split(string input, string delimiter)
{
	vector<int> output;
	size_t start = 0;
	size_t end = 0;

	while (start != string::npos && end != string::npos)
	{
		start = input.find_first_not_of(delimiter, end);

		if (start != string::npos)
		{
			end = input.find_first_of(delimiter, start);

			if (end != string::npos)
			{
				output.push_back(atoi(input.substr(start, end - start).c_str()));
			}
			else
			{
				output.push_back(atoi(input.substr(start).c_str()));
			}
		}
	}

	return output;

}

/************************************************************************************************
*
*	Clip
*	Get objects in Viewport
*	viewport
*	rootNode
*	listObject: List Object in viewport
*
*************************************************************************************************/
void Method::Clip(RECT viewport, QNode* rootNode, vector<GameObject*> &listObjects)
{
	// if viewport has collision with Node
	if (RectToRect(viewport, rootNode->Rect()))
	{
		// if Node hasn't 4 child Node, add listObject of Node into listObject
		if (rootNode->ChildNode.size() == 0)
			listObjects.insert(listObjects.end(), rootNode->ListObjs.begin(), rootNode->ListObjs.end());
		else
		{
			// Call Clip with 4 Child Node
			Clip(viewport, rootNode->ChildNode[0], listObjects);
			Clip(viewport, rootNode->ChildNode[1], listObjects);
			Clip(viewport, rootNode->ChildNode[2], listObjects);
			Clip(viewport, rootNode->ChildNode[3], listObjects);
		}
	}
}

/************************************************************************************************
*
*	Clip
*	Get objects in Viewport
*	path: File path
*	stringObjects:	 vector save strings of Objects
*	stringUpdate:	 vector save strings of Nodes Update
*	stringRender:	 vertor save strings of Nodes Render
*
*************************************************************************************************/
void Method::ReadInfoFromFile(string path, vector<string> &stringObjects, vector<string> &stringUpdate, vector<string> &stringRender)
{
	string line;
	ifstream myMethod(path);
	vector<string> listLine;

	// Read Line
	if (myMethod.is_open())
	{
		while (getline(myMethod, line))
		{
			listLine.push_back(line);
		}
		myMethod.close();
	}

	// Set index to identified kind string
	int index = 0;
	for each (string o in listLine)
	{
		if (o == "List Objects")
		{
			index = 1;
			continue;
		}
		else
		{
			if (o == "Tree Update")
			{
				index = 2;
				continue;
			}
			else if (o == "Tree Render")
			{
				index = 3;
				continue;
			}
		}

		// Add string into vector
		switch (index)
		{
		case 1:
			stringObjects.push_back(o);
			break;
		case 2:
			stringUpdate.push_back(o);
			break;
		case 3:
			stringRender.push_back(o);
			break;
		default:
			break;
		}
	}
}

/************************************************************************************************
*
*	AllObjects
*	Create all objects
*	list:	 vector save strings of Objects
*	res:	 pointer Resources
*	mario:	 pointer of Mario 's Object
*
*************************************************************************************************/
map<int, GameObject*> Method::AllObjects(vector<string> &list, Resources* &res, Mario* &mario)
{
	// std::map save all Objects with key
	map<int, GameObject*> ListObjects;

	// get size list string
	// a element in list string save info of a Object
	int n = list.size();

	// Create Object by ID and add it into std::map
	for (int i = 0; i < n; i++)
	{
		// get info of object
		vector<int> listNum = Split(list[i], "\t");

		// Create pointer object
		GameObject* o = NULL;

		// Create object by Id
		switch (listNum[1])
		{
		case ID::ID_Mario:
			if (mario == NULL)
				mario = new Mario(listNum[2], listNum[3], 50, 65, 1, res, listNum[0], 1);
			else
			{
				mario->SetPosX(listNum[2]);
				mario->SetPosY(listNum[3]);
			}
			break;
		case ID::ID_Land:
			o = new Land(listNum[2], listNum[3], 50, 50, res, ID::ID_Land, listNum[0], 3);
			break;
		case ID::ID_StoneA:
			o = new Land(listNum[2], listNum[3], 50, 50, res, ID::ID_StoneA, listNum[0], 3);
			break;
		case ID::ID_StoneB:
			o = new Land(listNum[2], listNum[3], 50, 50, res, ID::ID_StoneB, listNum[0], 3);
			break;
		case ID::ID_Brick:
			o = new Brick(listNum[2], listNum[3], 50, 50, res, listNum[0], 3);
			break;
		case ID::ID_Flower:
			o = new Qbrick(listNum[2], listNum[3], 50, 50, res, ID::ID_Flower, listNum[0], 3);
			break;
		case ID::ID_MushGreen:
			o = new Qbrick(listNum[2], listNum[3], 50, 50, res, ID::ID_MushGreen, listNum[0], 3);
			break;
		case ID::ID_MushRed:
			o = new Qbrick(listNum[2], listNum[3], 50, 50, res, ID::ID_MushRed, listNum[0], 3);
			break;
		case ID::ID_Star:
			o = new Qbrick(listNum[2], listNum[3], 50, 50, res, ID::ID_Star, listNum[0], 3);
			break;
		case ID::ID_Coin:
			o = new Coin(listNum[2], listNum[3], 50, 50, res, listNum[0], 2);
			break;
		case ID::ID_QBrick:
			o = new Qbrick(listNum[2], listNum[3], 50, 50, res, ID::ID_Coin, listNum[0], 3);
			break;
		case ID::ID_Goomba:
			o = new Goomba(listNum[2], listNum[3], 50, 45, res, listNum[0], 2);
			break;
		case ID::ID_EatFlower:
			o = new EatFlower(listNum[2], listNum[3], 50, 51, res, listNum[0], 4);
			break;
		case ID::ID_Koopa:
			o = new Koopa(listNum[2], listNum[3], 50, 85, res, listNum[0], 2);
			break;
		case ID::ID_Pipe1:
			o = new Pipe(listNum[2], listNum[3], 75, 50, res, ID::ID_Pipe1, listNum[0], 3);
			break;
		case ID::ID_Pipe2:
			o = new Pipe(listNum[2], listNum[3], 75, 100, res, ID::ID_Pipe2, listNum[0], 3);
			break;
		case ID::ID_Pipe3:
			o = new Pipe(listNum[2], listNum[3], 75, 200, res, ID::ID_Pipe3, listNum[0], 3);
			break;
		case ID::ID_Mountain:
			o = new Scene(listNum[2], listNum[3], 398, 160, res, ID::ID_Mountain, listNum[0], 4);
			break;
		case ID::ID_PipeSpecial:
			o = new Pipe(listNum[2], listNum[3], 75, 200, res, ID::ID_PipeSpecial, listNum[0], 1);
			break;
		case ID::ID_House:
			o = new Scene(listNum[2], listNum[3], 208, 295, res, ID::ID_House, listNum[0], 4);
			break;
		case ID::ID_Cloud:
			o = new Scene(listNum[2], listNum[3], 64, 50, res, ID::ID_Cloud, listNum[0], 4);
			break;
		case ID::ID_Grass:
			o = new Scene(listNum[2], listNum[3], 64, 50, res, ID::ID_Grass, listNum[0], 4);
			break;
		case ID::ID_Ladder:
			o = new Ladder(listNum[2], listNum[3], 150, 25, res, listNum[0], 4);
			break;
		case ID::ID_Boss:
			o = new Boss(listNum[2], listNum[3], 80, 90, res, listNum[0], 4);
			break;
		default:
			break;
		}

		if (o != NULL)
			ListObjects.insert(std::pair<int, GameObject*>(listNum[0], o));
	}

	return ListObjects;
}

/************************************************************************************************
*
*	AllNode
*	Create all nodes
*	list:			vector save strings of Node
*	listObjects:	All Objects in Game
*
*************************************************************************************************/
map<double, QNode*> Method::AllNode(vector<string> &list, map<int, GameObject*> &listObjects)
{
	// std::map save all node with key
	map<double, QNode*> ListNode;

	// get size list string
	// a element of vector save info of Node
	int n = list.size();

	for (int i = 0; i < n; i++)
	{
		// get info of object
		vector<int> listNum = Split(list[i], "\t");

		// Create Node
		QNode* node = new QNode(listNum, listObjects);

		// Add Node into std::map
		ListNode.insert(std::pair<double, QNode*>(node->Id(), node));
	}

	return ListNode;
}

/************************************************************************************************
*
*	BuildTree
*	Build Tree All Node
*	listNode:	All nodes
*	rootNode:	Root Node
*
*************************************************************************************************/
void Method::BuildTree(map<double, QNode*> &listNode, QNode* &rootNode)
{
	// save id of 4 child nodes
	double leftTop, rightTop, leftBottom, rightBottom, id;

	// get number of nodes
	int n = listNode.size();

	// set pointer of root node with 4 child node
	for (map<double, QNode*>::iterator i = listNode.begin(); i != listNode.end(); ++i)
	{
		// get id of Node
		id = i->first;
		
		// if id == 1, set pointer for Root Node
		if (id == 1)
			rootNode = i->second;

		// Calcutate id of 4 child node
		leftTop = id * 10 + 0;
		rightTop = id * 10 + 1;
		rightBottom = id * 10 + 2;
		leftBottom = id * 10 + 3;

		// find child node
		map<double, QNode*>::iterator m = listNode.find(leftTop);

		// if ok
		if (m != listNode.end())
		{
			// add 4 child node in to list childe node
			i->second->ChildNode.push_back(m->second);
			i->second->ChildNode.push_back(listNode.find(rightTop)->second);		
			i->second->ChildNode.push_back(listNode.find(rightBottom)->second);
			i->second->ChildNode.push_back(listNode.find(leftBottom)->second);
		}
	}
}

/************************************************************************************************
*
*	GetObjectInViewport
*	Get all objects in viewport
*	viewport:	All nodes
*	rootNode:	Root Node
*	list:		List dynamic objects
*	listStatic:	List static objects
*
*************************************************************************************************/
void Method::GetObjectInViewport(RECT rect, QNode* rootNode, vector<GameObject*> &list, vector<GameObject*> &listStatic)
{
	// save all objects in viewport
	vector<GameObject*> listObjects;

	// resize viewport to get more objects
	rect.left -= 300;
	rect.right += 300;

	// Call method Clip to get objects in viewport
	Clip(rect, rootNode, listObjects);

	// add object into list dynamic or static
	for each (GameObject* o in listObjects)
	{

		if (o->GetIsStatic())
		{
			auto f = find_if(begin(listStatic), end(listStatic), [&](GameObject* obj)
			{
				return obj == o;
			});

			if (f == end(listStatic) && o->GetIsAlive() == true)
				listStatic.push_back(o);
		}

		else
		{
			auto f = find_if(begin(list), end(list), [&](GameObject* obj)
			{
				return obj == o;
			});

			if (f == end(list) && o->GetIsAlive() == true)
				list.push_back(o);
		}
	}

	// clear listObjects
	listObjects.clear();
}

/************************************************************************************************
*
*	BuildTree
*	Build Tree from All Objects and All Node
*	path:		File map
*	res:		pointer Resources
*	TreeUpdate:	Root node of Tree Update
*	TreeRender:	Root node o Tree Render
*	mario:		pointer Mario 's Object
*
*************************************************************************************************/
void Method::BuildTree(string path, Resources* res, QNode* &TreeUpdate, QNode* &TreeRender, Mario* &mario)
{
	// get info of objects, node in file map
	vector<string> stringObjects, stringUpdate, stringRender;
	ReadInfoFromFile(path, stringObjects, stringUpdate, stringRender);

	// Create all objects
	map<int, GameObject*> ListObjects = AllObjects(stringObjects, res, mario);
	
	// Create all Node Update
	map<double, QNode*> AllNodeUpdate = AllNode(stringUpdate, ListObjects);
	
	// Create all Node Render
	map<double, QNode*> AllNodeRender = AllNode(stringRender, ListObjects);

	// Build Tree Update
	BuildTree(AllNodeUpdate, TreeUpdate);
	
	// Build Tree Render
	BuildTree(AllNodeRender, TreeRender);
}

/************************************************************************************************
*
*	SaveTree
*	Save Tree into file
*	path:		File map
*	TreeUpdate:	Root node of Tree Update
*	TreeRender:	Root node o Tree Render
*
*************************************************************************************************/
void Method::SaveTree(string path, QNode* &TreeUpdate, QNode* &TreeRender, Mario* mario)
{
	// Get All Object
	vector<GameObject*> listObjects;
	GetAllObject(listObjects, TreeRender);
	
	vector<QNode*> listNodesUpdate;
	GetAllNode(listNodesUpdate, TreeUpdate);

	vector<QNode*> listNodesRender;
	GetAllNode(listNodesRender, TreeRender);
	
	// Save All Object
	ofstream of(path);
	of.clear();

	// Save Size Map
	of << GAME_WORLD << "\t" << GAME_LIVE << "\n";
	
	// Save string "List Objects"
	of << "List Objects" << "\n";

	// Save Mario, because Mario isn't in Tree
	of << mario->Number() << "\t" << mario->ID() << "\t" << (int)mario->PosX() << "\t" << (int)mario->PosY() + 100 << "\n";

	// Save List Objects
	for each (GameObject* o in listObjects)
	{
		//if (o->GetIsAlive() == false) continue;
		of << o->Number() << "\t" << o->ID() << "\t" << (int)o->PosX() << "\t" << (int)o->PosY() << "\n";
	}

	// Save string "Tree Update"
	of << "Tree Update" << "\n";
	for each (QNode* node in listNodesUpdate)
	{
		int id = (int)node->Id();
		int x = node->Rect().left;
		int y = node->Rect().top;
		int w = node->Rect().right - x;
		int h = y - node->Rect().bottom;

		of << to_string(id) << "\t" << x << "\t" << y << "\t" << w << "\t" << h << "\t";

		for each (GameObject* o in node->ListObjs)
		{
			of << o->Number() << "\t";
		}
		of << "\n";
	}
	
	// Save string "Tree Render"
	of << "Tree Render" << "\n";
	for each (QNode* node in listNodesRender)
	{
		int id = (int)node->Id();
		int x = node->Rect().left;
		int y = node->Rect().top;
		int w = node->Rect().right - x;
		int h = y - node->Rect().bottom;

		of << to_string(id) << "\t" << x << "\t" << y << "\t" << w << "\t" << h << "\t";

		for each (GameObject* o in node->ListObjs)
		{
			of << o->Number() << "\t";
		}
		of << "\n";
	}
	of.close();
}

// Save Tree
void Method::GetAllNode(vector<QNode*> &listNode, QNode* &Tree)
{
	listNode.push_back(Tree);

	if (Tree->ChildNode.size() > 0)
	{
		GetAllNode(listNode, Tree->ChildNode[0]);
		GetAllNode(listNode, Tree->ChildNode[1]);
		GetAllNode(listNode, Tree->ChildNode[2]);
		GetAllNode(listNode, Tree->ChildNode[3]);
	}
}

/************************************************************************************************
*
*	RectToRect
*	Check coliision between rectangle to rectangle
*	A:	Rectangle 1
*	B:	Rectangle 2
*
*************************************************************************************************/
bool Method::RectToRect(RECT A, RECT B)
{
	if (A.right < B.left || A.left > B.right || A.bottom > B.top || A.top < B.bottom) 
		return false;
	
	return true;
}

/************************************************************************************************
*
*	IsCollision
*	Check coliision between 2 Game Object
*	A:	GameObject 1
*	B:	GameObject 2
*
*************************************************************************************************/
DirCollision Method::IsCollision(GameObject* A, GameObject* B, float &colTime)
{
	// Rect of GameObject A
	RECT rectA = A->OldRect();

	// Rect of GameObject B
	RECT rectB = B->Rect();

	// A Big Rect Bounding Old and Current Rect of GameOject A
	RECT bigA = RectBoundCurrentAndOldRect(A);
	RECT bigB = RectBoundCurrentAndOldRect(B);

	// If a Big Rect not collision with rect B
	// Object A don't collision with Object B
	if (RectToRect(bigA, bigB) == false) return DirCollision::NONE;

	if (A->ID() == ID::ID_Mario && B->ID() == ID::ID_Goomba)
	{
		int i = 0;
	}
	float txEntry, txExit, dxEntry, dxExit;
	float tyEntry, tyExit, dyEntry, dyExit;

	// Calculate Vx and Vy 
	// Number of the pixels Object can move in dt second
	// If v < 0, Object move right to left
	float Vx = (A->PosX() - A->OldPosX()) - (B->PosX() - B->OldPosX());
	float Vy = (A->PosY() - A->OldPosY()) - (B->PosY() - B->OldPosY());

	// Find the distance between the objects
	if (Vx > 0)
	{
		dxEntry = rectB.left - rectA.right;
		dxExit = rectB.right - rectA.left;
	}
	else
	{
		dxEntry = rectB.right - rectA.left;
		dxExit = rectB.left - rectA.right;
	}

	if (Vy > 0)
	{
		dyEntry = rectB.bottom - rectA.top;
		dyExit = rectB.top - rectA.bottom;
		
	}
	else
	{
		dyEntry = rectB.top - rectA.bottom;
		dyExit = rectB.bottom - rectA.top;
	}


	// Calculate txEntry and tyEntry
	if (Vx == 0)
	{
		txEntry = -std::numeric_limits<float>::infinity();
		txExit = std::numeric_limits<float>::infinity();
	}
	else
	{
		txEntry = dxEntry / Vx;
		txExit = dxExit / Vx;
	}

	if (Vy == 0)
	{
		tyEntry = -std::numeric_limits<float>::infinity();
		tyExit = std::numeric_limits<float>::infinity();
	}
	else
	{
		tyEntry = dyEntry / Vy;
		tyExit = dyExit / Vy;
	}
	
	// Find the earliest/latest times of collision
	float EntryTime = max(txEntry, tyEntry);
	float ExitTime = min(txExit, tyExit);

	// If there was no collision
	if (EntryTime > ExitTime || txEntry < 0.0f && tyEntry < 0.0f || txEntry > 1.0f || tyEntry > 1.0f)
	{
		// Not collision
		return DirCollision::NONE;
	}
	else
	{
		// Assigned Collision Time
		colTime = EntryTime;

		// Have collision
		// Find direction collision
		if (txEntry > tyEntry)
		{
			if (dxEntry < 0.0f)
				return DirCollision::LEFT;
			else
				return DirCollision::RIGHT;
		}
		else
		{
			if (dyEntry <= 0.0f)
				return DirCollision::BOTTOM;
			else
				return DirCollision::TOP;
		}
	}
}

/************************************************************************************************
*
*	RectBoundCurrentAndOldRect
*	Create a Rect bounding current rect and old rect of Object
*	A:	GameObject 1
*	B:	GameObject 2
*
*************************************************************************************************/
RECT Method::RectBoundCurrentAndOldRect(GameObject* object)
{
	RECT result, current, old;
	current = object->Rect();
	old = object->OldRect();

	// Left
	if (old.left < current.left)
		result.left = old.left;
	else
		result.left = current.left;

	// Right
	if (old.right > current.right)
		result.right = old.right;
	else
		result.right = current.right;

	// Top
	if (old.top > current.top)
		result.top = old.top;
	else
		result.top = current.top;

	// Bottom
	if (old.bottom < current.bottom)
		result.bottom = old.bottom;
	else
		result.bottom = current.bottom;

	return result;
}

/************************************************************************************************
*
*	MinDistance
*	Compare Object by min Time Collision
*	A:	GameObject 1
*	B:	GameObject 2
*
*************************************************************************************************/
bool Method::MinDistance(GameObject* A, GameObject* B)
{
	return (A->TimeCollision < B->TimeCollision);
}

/************************************************************************************************
*
*	MinDepth
*	compare Object by min depth
*	A:	GameObject 1
*	B:	GameObject 2
*
*************************************************************************************************/
bool Method::MaxDepth(GameObject* A, GameObject* B)
{
	return (A->Depth() > B->Depth());
}

// Get All Objects
void Method::GetAllObject(vector<GameObject*> &list, QNode* &Tree)
{
	if (Tree->ListObjs.size() > 0)
		list.insert(list.end(), Tree->ListObjs.begin(), Tree->ListObjs.end());

	if (Tree->ChildNode.size() > 0)
	{
		GetAllObject(list, Tree->ChildNode[0]);
		GetAllObject(list, Tree->ChildNode[1]);
		GetAllObject(list, Tree->ChildNode[2]);
		GetAllObject(list, Tree->ChildNode[3]);
	}
}